package scatter.common.rest.advise;


import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException;
import org.springframework.core.annotation.Order;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import scatter.common.pojo.vo.ErrorVo;
import scatter.common.rest.exception.*;
import scatter.common.rest.interceptor.GlobalInterceptor;
import scatter.common.rest.tools.InterfaceTool;
import scatter.common.rest.tools.ThreadContextTool;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.sql.SQLSyntaxErrorException;


/**
 * controller 异常统一处理类
 * 不要在该类的任何地方抛出异常，因为这里就是处理异常的地方，如果这里抛出异常，将会被转发到BasicErrorController.error方法处理
 * 注意这里处理的返回结果也会进入到GlobalResponseBodyAdvice里处理
 * Created by yangwei
 * Created at 2019/7/25 20:24
 */
@RestControllerAdvice
@Slf4j
@Order(10)
public class GlobalExceptionAdvice implements InterfaceTool {

    public static ErrorVo createRM(Integer errorCode, String errorMsg, Object data, Exception e) {
        log.error("请求错误信息: errorCode={},errorMsg={},data={},exceptionMsg={},exception={} ",errorCode, errorMsg, data,e.getMessage(), e.getClass().getName());
        if(e instanceof BusinessException){
            if(!((BusinessException) e).isHttp()){
                log.warn("业务异常内容",e);
            }
        }else {
            log.warn("异常内容",e);
        }
        ErrorVo errorVo = new ErrorVo();
        errorVo.setErrorCode(errorCode);
        errorVo.setErrorMsg(errorMsg);
        errorVo.setPayload(data);
        return errorVo;
    }


    /**
     * 运行时异常
     * 该异常没有打印异常日志，应该是可预知的异常
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorVo handleBusinessException(HttpServletRequest request, BusinessException ex) {
        return createRM(ex.getCode(), ex.getMessage(), ex.getPayload(), ex);
    }

    /**
     * 无数据权限异常
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(BusinessDataNoPrivilegeException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorVo handleBusinessDataNoPrivilegeException(HttpServletRequest request, BusinessDataNoPrivilegeException ex) {
        return createRM(ex.getCode(), ex.getMessage(), ex.getPayload(), ex);
    }
    /**
     * 数据被他人修改异常
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(BusinessDataModifiedByOtherException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorVo handleBusinessDataModifiedByOtherException(HttpServletRequest request, BusinessDataModifiedByOtherException ex) {
        return createRM(ex.getCode(), ex.getMessage(), ex.getPayload(), ex);
    }
    /**
     * 自定义的404异常
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(BusinessDataNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorVo handleBusinessDataNotFoundException(HttpServletRequest request, BusinessDataNotFoundException ex) {
        return createRM(ex.getCode(), ex.getMessage(), ex.getPayload(), ex);
    }

    /**
     * 不支持的媒体类型
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    public ErrorVo handleHttpMediaTypeNotSupportedException(HttpServletRequest request, HttpMediaTypeNotSupportedException ex) {
        return createRM(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), ex.getMessage(), null, ex);
    }

    /**
     * 未提供参数异常
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorVo handleMissingServletRequestParameterException(HttpServletRequest request, MissingServletRequestParameterException ex) {
        String msg = ex.getParameterName() + "不能为空";

        return createRM(HttpStatus.BAD_REQUEST.value(), msg, null, ex);
    }

    /**
     * 表单验证不对过异常响应
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorVo handleMethodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException ex) {
        // 在脚本验证是没有指定reportOn会有全局异常
        String msg = "";
        FieldError fieldError = ex.getBindingResult().getFieldError();
        if (fieldError != null) {
            msg = fieldError.getDefaultMessage();
        }else {
            msg = ex.getBindingResult().getGlobalError().getDefaultMessage();
        }

        return createRM(HttpStatus.BAD_REQUEST.value(), msg, null, ex);
    }
    /**
     * 表单验证不对过异常响应
     * 发现非requestBody 的get方法表单中有验证时会抛出这个异常
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorVo handleBindException(HttpServletRequest request, BindException ex) {
        String msg = ex.getBindingResult().getFieldError().getDefaultMessage();
        return createRM(HttpStatus.BAD_REQUEST.value(), msg, null, ex);
    }
    /**
     * 表单验证不对过异常响应
     * 发现非requestBody 的get方法表单中有验证时会抛出这个异常以及使用spring 断言
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorVo handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException ex) {
        return createRM(HttpStatus.BAD_REQUEST.value(), ex.getMessage(), null, ex);
    }

    /**
     *  异常没有权限的异常
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public ErrorVo handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex) {
        boolean b = SecurityContextHolder.getContext().getAuthentication() != null &&
                SecurityContextHolder.getContext().getAuthentication().isAuthenticated() &&
                //when Anonymous Authentication is enabled
                !(SecurityContextHolder.getContext().getAuthentication()
                        instanceof AnonymousAuthenticationToken);
        //如果用户没有登录
        if (!b) {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            throw new UsernameNotFoundException("需要登录");

        }
        return createRM(HttpStatus.FORBIDDEN.value(), "没有权限", request.getRequestURI(), ex);
    }


    /**
     *  用户认证异常
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(AuthenticationException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ErrorVo handleAuthenticationException(HttpServletRequest request, AuthenticationException ex) {
        return createRM(HttpStatus.UNAUTHORIZED.value(), ex.getMessage(), request.getRequestURI(), ex);
    }
    /**
     *  用户认证异常
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(AuthenticationCredentialsNotFoundException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ErrorVo handleAuthenticationCredentialsNotFoundException(HttpServletRequest request, AuthenticationCredentialsNotFoundException ex) {
        return createRM(HttpStatus.UNAUTHORIZED.value(), "用户未登录", request.getRequestURI(), ex);
    }

    /**
     *  用户未登录异常
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(UserNotLoginException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ErrorVo handleUserNotLoginException(HttpServletRequest request, UserNotLoginException ex) {
        return createRM(HttpStatus.UNAUTHORIZED.value(), ex.getMessage(), request.getRequestURI(), ex);
    }
    /**
     *  登录帐号未知异常
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(UsernameNotFoundException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ErrorVo handleUsernameNotFoundException(HttpServletRequest request, UsernameNotFoundException ex) {
        return createRM(HttpStatus.UNAUTHORIZED.value(), "帐号不正确", request.getRequestURI(), ex);
    }

    /**
     * shiro 登录密码不正确
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(BadCredentialsException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ErrorVo handleBadCredentialsException(HttpServletRequest request, BadCredentialsException ex) {
        return createRM(HttpStatus.UNAUTHORIZED.value(), "密码不正确", request.getRequestURI(), ex);
    }

    /**
     *  登录帐号已被锁定
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(LockedException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ErrorVo handleLockedAccountException(HttpServletRequest request, LockedException ex) {
        return createRM(HttpStatus.UNAUTHORIZED.value(), "帐号已被锁定", request.getRequestURI(), ex);
    }

    /**
     * 不支持的请求方法
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public ErrorVo handleHttpRequestMethodNotSupportedException(HttpServletRequest request, HttpRequestMethodNotSupportedException ex) {
        return createRM(HttpStatus.METHOD_NOT_ALLOWED.value(), "不支持的请求方法" + request.getMethod(), request.getRequestURI(), ex);
    }

    /**
     * sql异常，也可能是数据不对导致的sql执行问题
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(DataIntegrityViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorVo handleDataIntegrityViolationException(HttpServletRequest request, DataIntegrityViolationException ex) {
        return createRM(HttpStatus.BAD_REQUEST.value(), "数据库请求错误" + ex.getCause().getMessage(), request.getRequestURI(), ex);
    }
    /**
     * sql异常，也可能是数据不对导致的sql执行问题
     * 表不存在也会报这个异常
     * 但这个异常经调式在表不存在是会被 BadSqlGrammarException 异常包装
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(SQLSyntaxErrorException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorVo handleSQLSyntaxErrorException(HttpServletRequest request, SQLSyntaxErrorException ex) {
        return createRM(HttpStatus.INTERNAL_SERVER_ERROR.value(), "数据库请求错误" + ex.getCause().getMessage(), request.getRequestURI(), ex);
    }
    /**
     * sql异常，也可能是数据不对导致的sql执行问题
     * 表不存在也会报这个异常
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(BadSqlGrammarException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorVo handleBadSqlGrammarException(HttpServletRequest request, BadSqlGrammarException ex) {
        return createRM(HttpStatus.INTERNAL_SERVER_ERROR.value(), "数据库请求错误" + ex.getCause().getMessage(), request.getRequestURI(), ex);
    }

    /**
     * sql异常，违反数据库约束，如：唯一索引
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(DuplicateKeyException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorVo handleDuplicateKeyException(HttpServletRequest request, DuplicateKeyException ex) {
        return createRM(HttpStatus.BAD_REQUEST.value(), "违反数据库约束" + ex.getCause().getMessage(), request.getRequestURI(), ex);
    }

    /**
     * 一般是后台@RequestBody接收参数，但客户端没有传导致
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorVo handleHttpMessageNotReadableException(HttpServletRequest request, HttpMessageNotReadableException ex) {
        return createRM(HttpStatus.BAD_REQUEST.value(), "没有可用参数或参数格式不正确", request.getRequestURI(), ex);
    }

    /**
     * 上传文件超过限制异常
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorVo handleMaxUploadSizeExceededException(HttpServletRequest request, MaxUploadSizeExceededException ex) {
        long size = ex.getMaxUploadSize();
        if (size < 0) {
            if (ex.getCause() instanceof IllegalStateException && ex.getCause().getCause() instanceof FileSizeLimitExceededException) {
                size = ((FileSizeLimitExceededException) ex.getCause().getCause()).getPermittedSize();
            }
        }
        ErrorVo rm = createRM(HttpStatus.BAD_REQUEST.value(), "超过上传限制，允许最大值" + size, request.getRequestURI(), ex);
        return rm;
    }
    /**
     * 其它不可预知的异常，通常定义为系统异常
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorVo handleException(HttpServletRequest request, Exception ex) {
        // 防止子类的情况
        String warnMsg = "如果有必要，你需要定义一个异常类，并继承 {}，以达到响应状态码的需求";
        if (ex instanceof BusinessException) {
            log.warn(warnMsg, BusinessException.class.getName());
            return handleBusinessException(request, (BusinessException) ex);
        }
        // 打印异常栈，方便定位问题
        log.info("请求有异常：errorDigest={},more information refer error log.",ex.getMessage());
        log.error("请求异常信息：errorMsg={}",ex.getMessage(), ex);
        ThreadContextTool.put(GlobalInterceptor.HAS_EXCEPTION_KEY,true);
        ThreadContextTool.put(GlobalInterceptor.EXCEPTION_KEY,ex);
        return createRM(HttpStatus.INTERNAL_SERVER_ERROR.value(), "系统内部异常", null, ex);
    }
}
